module ActiveRecordPatches
  def validates_boolean(*methods)
    unwrap_validation_args(methods, "must be boolean") do |value, options|
      (options[:allow_nil] && value.nil?) || (!value.nil? && (value == true || value == false || %w(true t 1 false f 0).include?(value.to_s.downcase)))
    end
  end
  
  def validates_email(*methods)
    unwrap_validation_args(methods, "is not a valid email address") do |value, options|
      (options[:allow_nil] && value.nil?) || (options[:allow_blank] && value.blank?) || (value && value.valid_email? && value.length < 255)
    end
  end
  
  def validates_multiple_email(*methods)
    unwrap_validation_args(methods, "contains an invalid email address") do |value, options|
      (options[:allow_nil] && value.nil?) || (options[:allow_blank] && value.blank?) || (value && value.split(' ').length == value.split(' ').map {|address| address =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }.compact.length )
    end
  end
  
  def validates_name(*methods)    
    unwrap_validation_args(methods, "must contain a first and last name separated by a space") do |value, options|
      # we allow a blank value, that way you can combine with validates_presence_of
      value.blank? || value.strip.split(' ').length > 1
    end
  end
  
  def default_value_for(method, new_value, options = {}, &block)
    unwrap_before_validation_args(method, options) do |record, method, value|
      record.send("#{method}=", (block ? block.call(record) : new_value)) if value.nil?
    end
  end
  
  def validate_length_shorter_than(method, maximum_length, options = {})
    unwrap_before_validation_args(method, options) do |record, method, value|
      record.send("#{method}=", value.to_s[0, maximum_length]) if !value.nil? && value.to_s.length > maximum_length
    end
  end
  
  private
  
  def unwrap_validation_args(methods, default_error_message)
    options = methods.last.is_a?(Hash) ? methods.pop : {}
    
    message = options[:message] || default_error_message
    
    send(validation_method(options[:on] || :save), options) do |record|
      methods.each do |method|
        record.errors.add(method, message) unless yield(record.send(method), options)
      end
    end
  end

  def unwrap_before_validation_args(methods, options)
    methods = [methods] unless methods.is_a?(Array)
    
    send(before_validation_method(options[:on]), options) do |record|
      methods.each { |method| yield(record, method, record.send(method)) }
  
      true
    end
  end

  def before_validation_method(on_action)
    on_action ||= :save
    
    on_action == :save ? "before_validation" : "before_validation_on_#{on_action}"
  end
end